package com.gitee.openviducn.inspector.manager;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gitee.openviducn.inspector.common.BizException;
import com.gitee.openviducn.inspector.common.ResponseError;
import com.gitee.openviducn.inspector.datasource.entity.OpenviduInfo;
import com.gitee.openviducn.inspector.datasource.mapper.OpenviduInfoMapper;
import com.gitee.openviducn.inspector.domain.vo.response.openvidu.OpenViduVO;
import com.gitee.openviducn.inspector.domain.vo.response.openvidu.SessionVO;
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import io.openvidu.java.client.OpenVidu;
import io.openvidu.java.client.OpenViduHttpException;
import io.openvidu.java.client.OpenViduJavaClientException;
import io.openvidu.java.client.Session;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * @Author: https://gitee.com/wesleyOne
 * @Date: 04.18 2020
 */
@Slf4j
@Component
public class OpenViduManager {

    /**
     * OpenVidu客户端对象维护
     * 由于OpenVidu对象没法序列化，所以只能自行维护
     */
    private static final Map<String, OpenVidu> OPEN_VIDU_MAP = new ConcurrentHashMap<>(128);

    @Getter
    private AsyncLoadingCache<String, OpenViduVO> openviduCache = Caffeine.newBuilder()
            .maximumSize(100)
            .refreshAfterWrite(2, TimeUnit.SECONDS)
            .buildAsync(this::refreshOpenViduVO);

    @Resource
    private OpenviduInfoMapper openviduInfoMapper;

    /**
     * 获取OpenVidu客户端对象
     */
    OpenVidu getOpenVidu(String hostname) {
        return OPEN_VIDU_MAP.get(hostname);
    }
    /**
     * 获取单个OpenVidu详情
     */
    public OpenViduVO getOpenViduVO(String hostname) throws ExecutionException, InterruptedException {
        CompletableFuture<OpenViduVO> openViduVOCompletableFuture = openviduCache.get(hostname);
        return openViduVOCompletableFuture.get();
    }

    /**
     * 强制刷新OpenVidu缓存
     */
    void forceRefreshOpenViduCache(String hostname) {
        openviduCache.synchronous().refresh(hostname);
    }

    /**
     * 强制刷新Session缓存
     */
    private void forceRefreshSessionCache(String hostname, Session session) {
        try {
            session.fetch();
        } catch (OpenViduJavaClientException e) {
            log.error("OpenVidu客户端操作异常",e);
        } catch (OpenViduHttpException e) {
            log.error("OpenVidu请求异常",e);
        }
        OpenVidu openVidu = OPEN_VIDU_MAP.get(hostname);
        if (Objects.isNull(openVidu)) {
            return;
        }
        OpenViduVO openViduVO = OpenViduVO.build(hostname, openVidu);
        openviduCache.synchronous().put(hostname, openViduVO);
    }

    /**
     * 用于刷新OpenVidu详情缓存
     */
    private OpenViduVO refreshOpenViduVO(String hostname) {
        if (!OPEN_VIDU_MAP.containsKey(hostname)) {
            LambdaQueryWrapper<OpenviduInfo> queryWrapper = new LambdaQueryWrapper<OpenviduInfo>()
                    .eq(OpenviduInfo::getHostname, hostname);
            OpenviduInfo openviduInfo = openviduInfoMapper.selectOne(queryWrapper);
            if (Objects.isNull(openviduInfo)) {
                return null;
            }
            OpenVidu openVidu = new OpenVidu(openviduInfo.getHostname(), openviduInfo.getSecret());
            OPEN_VIDU_MAP.put(hostname, openVidu);
        }
        OpenVidu openVidu = OPEN_VIDU_MAP.get(hostname);
        if (Objects.isNull(openVidu)) {
            return null;
        }
        try {
            boolean fetch = openVidu.fetch();
            log.info("refresh OpenViduVO: {}", fetch);
        } catch (OpenViduJavaClientException e) {
            log.error("OpenVidu客户端操作异常",e);
        } catch (OpenViduHttpException e) {
            log.error("OpenVidu请求异常",e);
        }
        return OpenViduVO.build(hostname, openVidu);
    }

    /**
     * 获取单个session详情
     */
    public SessionVO getSessionVO(String hostname, String sessionId) throws ExecutionException, InterruptedException {
        OpenViduVO openViduVO = getOpenViduVO(hostname);
        if (Objects.isNull(openViduVO)) {
            return null;
        }
        Optional<SessionVO> sessionVOOptional = openViduVO.getActiveSessions().stream().filter(s -> Objects.equals(sessionId, s.getSessionId())).findFirst();
        return sessionVOOptional.orElse(null);
    }


    private Optional<Session> getSession(String hostname, String sessionId) {
        OpenVidu openVidu = OPEN_VIDU_MAP.get(hostname);
        if (Objects.isNull(openVidu)) {
            return Optional.empty();
        }
        return openVidu.getActiveSessions().stream().filter(session -> Objects.equals(sessionId, session.getSessionId())).findFirst();
    }

    /**
     * 关闭指定session
     * 404: SESSION_ID不存在
     */
    public void closeSession(String hostname, String sessionId) throws OpenViduJavaClientException, BizException {
        Optional<Session> sessionOptional = getSession(hostname, sessionId);
        if (sessionOptional.isPresent()) {
            try {
                sessionOptional.get().close();
            } catch (OpenViduHttpException e) {
                int status = e.getStatus();
                String errMsg;
                if (status == 404) {
                    errMsg = "SESSION_ID不存在";
                } else {
                    errMsg = "未知异常";
                }
                throw new BizException(ResponseError.OPENVIDU_HTTP_ERR, errMsg);
            } finally {
                forceRefreshOpenViduCache(hostname);
            }
        }
    }

    /**
     * 强制断开指定连接connection
     * 400: 传递的SESSION_ID不存在
     * 404: 传递的CONNECTION_ID不存在
     */
    public void forceDisconnect(String hostname, String sessionId, String connectionId) throws OpenViduJavaClientException, OpenViduHttpException, BizException {
        Optional<Session> sessionOptional = getSession(hostname, sessionId);
        if (sessionOptional.isPresent()) {
            try {
                sessionOptional.get().forceDisconnect(connectionId);
            } catch (OpenViduHttpException e) {
                int status = e.getStatus();
                String errMsg;
                if (status == 400) {
                    errMsg = "SESSION_ID不存在";
                } else if (status == 404) {
                    errMsg = "CONNECTION_ID不存在";
                } else {
                    errMsg = "未知异常";
                }
                throw new BizException(ResponseError.OPENVIDU_HTTP_ERR, errMsg);
            } finally {
                forceRefreshSessionCache(hostname,sessionOptional.get());
            }
        }
    }

    /**
     * 强制取消指定发布流streamId
     * 400: 传递的SESSION_ID不存在
     * 404: 传递的STREAM_ID不存在
     * 405: 不允许直接删除IPCAM参与者的流
     */
    public void forceUnpublish(String hostname, String sessionId, String publishStreamId) throws OpenViduJavaClientException, BizException {
        Optional<Session> sessionOptional = getSession(hostname, sessionId);
        if (sessionOptional.isPresent()) {
            try {
                sessionOptional.get().forceUnpublish(publishStreamId);
            } catch (OpenViduHttpException e) {
                int status = e.getStatus();
                String errMsg;
                if (status == 400) {
                    errMsg = "SESSION_ID不存在";
                } else if (status == 404) {
                    errMsg = "STREAM_ID不存在";
                } else if (status == 405) {
                    errMsg = "不允许直接删除IPCAM参与者的流";
                } else {
                    errMsg = "未知异常";
                }
                throw new BizException(ResponseError.OPENVIDU_HTTP_ERR, errMsg);
            } finally {
                forceRefreshSessionCache(hostname, sessionOptional.get());
            }
        }
    }

}
